package analizadorCadena;

import gramatica.ParteDerecha;
import gramaticaPrecedencia.Tabla;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import simbolo.NoTerminal;
import simbolo.Simbolo;

public class Analizador {

	private Tabla tabla;
	private Character simboloInicial;
	private LinkedList<String> derivacion;
	private Map<NoTerminal, Set<ParteDerecha>> producciones;
	private Map<ParteDerecha, Simbolo> partesDerechas;

	private static final Character SIMBOLO_DISTINGUIDO = '$';
	private static final Character MAYOR = '>';
	private static final Character MENOR = '<';
	private static final Character IGUAL = '=';
	
	public Analizador(Tabla tabla,Map<NoTerminal, Set<ParteDerecha>> producciones, Character simboloInicial){
		this.tabla = tabla;
		this.producciones = producciones;
		this.simboloInicial = simboloInicial;
		this.derivacion = new LinkedList<String>();
		this.partesDerechas = new HashMap<ParteDerecha, Simbolo>();
		Iterator<NoTerminal> it = producciones.keySet().iterator();
		NoTerminal ntActual;
		while(it.hasNext()){
			ntActual = it.next(); 			
			Set<ParteDerecha> valores = producciones.get(ntActual);
			Iterator<ParteDerecha> itPD = valores.iterator();
			while(itPD.hasNext()){
				ParteDerecha parteDerechaAct = itPD.next();
				partesDerechas.put(parteDerechaAct,ntActual);
			}
		}
	}
	
	public Boolean cadenaValida(String cadenaIn){
		Boolean isValid = false;
		int simbolosMenor = 0;
		Character simboloActual;
		Character ultimoAgregado = SIMBOLO_DISTINGUIDO;
		Character simboloTabla = null;
		int i = 0; 
		int longitud = cadenaIn.length();
		StringBuffer workingString = new StringBuffer();
		workingString.append(SIMBOLO_DISTINGUIDO);
		StringBuffer cadena = new StringBuffer(cadenaIn);
		Character simboloNuevoTabla = null;
		cadena.append(SIMBOLO_DISTINGUIDO);
		Simbolo ultimoAgregadoSimbolo= null;
		Simbolo simboloActualSimbolo= null;
		String nuevaSubCadena = "";
		Simbolo parteIzquierda;
		ParteDerecha parteDerecha;
		
		while(i<longitud){
			simboloActual = cadena.charAt(i);
			ultimoAgregadoSimbolo= new Simbolo(ultimoAgregado);
			simboloActualSimbolo= new Simbolo(simboloActual);
//			se compara el simbolo actual contra el ultimo que se agrego al workingString. 
//			El simbolo actual es la columna y el ultimo la fila.
			
			simboloTabla = this.tabla.getRelacion(ultimoAgregadoSimbolo, simboloActualSimbolo);
			if(simboloTabla == null){
				this.getFailureInfo(workingString.toString(),cadena.substring(i,cadena.length()-1));
				return false;
			}
			if(simboloTabla.equals(IGUAL)){
				workingString.append(IGUAL);
				workingString.append(simboloActual);
				ultimoAgregado = simboloActual;
				i++;
			}else if(simboloTabla.equals(MENOR)){
				workingString.append(MENOR);
				workingString.append(simboloActual);
				ultimoAgregado = simboloActual;
				simbolosMenor++;
				i++;
			}else if(simboloTabla.equals(MAYOR) && simbolosMenor>0){
//				Obtenemos los simbolos que hay entre <>
				int indice = workingString.length()-1;
				while(indice>0 && !((Character)workingString.charAt(indice)).equals(MENOR)){
					indice--;
				}
//				Quitamos y guardamos la subcadena que buscaremos en las producciones. 
				parteDerecha = ParteDerecha.getFromString(workingString.substring(indice + 1, workingString.length()));
				workingString.delete(indice, workingString.length());
				ultimoAgregado = workingString.charAt(indice-1);
				simbolosMenor--;
//				Ahora la buscamos dentro de las producciones.
	
				parteIzquierda = getParteIzquierdaFromMap(parteDerecha);
				if(parteIzquierda != null){
					derivacion.addLast(parteDerecha.toString());
					derivacion.addLast(parteIzquierda.toString());
					simboloNuevoTabla = this.tabla.getRelacion(new Simbolo(ultimoAgregado), parteIzquierda);
					if(simboloNuevoTabla == null){
						this.getFailureInfo(workingString.toString(),cadena.substring(i,cadena.length()-1));
						return false;
					}
					nuevaSubCadena = parteIzquierda.toString();
					ultimoAgregado = parteIzquierda.getCaracter();
				}else {
					this.getFailureInfo(workingString.toString(),cadena.substring(i,cadena.length()-1));
					return false;
				}
				
//				Ahora le agregamos al workingString los nuevos simbolos dados por la reduccion.
				workingString.append(simboloNuevoTabla + nuevaSubCadena);
				if(simboloNuevoTabla.equals(MENOR)){
					simbolosMenor++;
				}
			} else {
				this.getFailureInfo(workingString.toString(),cadena.substring(i,cadena.length()-1));
				return false;
			}
		}
		
//		Si la cadena es valida esta deberia ser la ultima reduccion.
		while(simbolosMenor >= 1){
			int indice = workingString.length()-1;
			while(indice>0 && !((Character)workingString.charAt(indice)).equals(MENOR)){
				indice--;
			}
			parteDerecha = 
				ParteDerecha.getFromString(workingString.substring(indice + 1, workingString.length()));
			if(parteDerecha == null){
				this.getFailureInfo(workingString.toString(),cadena.substring(i,cadena.length()-1));
				return false;
			}
			workingString.delete(indice, workingString.length());
			ultimoAgregado = workingString.charAt(indice-1);
			simbolosMenor--;
			nuevaSubCadena = "";
			parteIzquierda = getParteIzquierdaFromMap(parteDerecha);
			if(parteIzquierda != null){
				derivacion.addLast(parteDerecha.toString());
				derivacion.addLast(parteIzquierda.toString());
				if(workingString.toString().equals(String.valueOf(SIMBOLO_DISTINGUIDO)) && simbolosMenor == 0 && parteIzquierda.getCaracter().equals(simboloInicial)){
					System.out.println("La cadena corresponde a la gramatica.");
					this.getDerivaciones();
					return true;
					
				} else {
					
					simboloNuevoTabla = this.tabla.getRelacion(new Simbolo(ultimoAgregado), parteIzquierda);
					if(simboloNuevoTabla == null){
						this.getFailureInfo(workingString.toString(),cadena.substring(i,cadena.length()-1));
						return false;
					}
					nuevaSubCadena = parteIzquierda.toString();
					ultimoAgregado = parteIzquierda.getCaracter();
				}
			}else {
				this.getFailureInfo(workingString.toString(),cadena.substring(i,cadena.length()-1));
				return false;
			}
			
			workingString.append(simboloNuevoTabla + nuevaSubCadena);
			if(simboloNuevoTabla.equals(MENOR)){
				simbolosMenor ++;
			}
		}
		this.getFailureInfo(workingString.toString(),cadena.substring(i,cadena.length()-1));
		return isValid;
	}
	
	private void getFailureInfo(String stack, String cadena){
		System.out.println("La cadena no es aceptada.");
		this.getDerivaciones();
		System.out.println("   ");
		System.out.println("Stack: " + "  " + stack);
		System.out.println("Cadena: " + "  " + cadena);
	}
	
	private void getDerivaciones(){
		Iterator<String> it = derivacion.descendingIterator();
		System.out.println();
		while(it.hasNext()){
			System.out.print(it.next());
			System.out.print("  -->  ");
			System.out.print(it.next());
			if(it.hasNext()){
				System.out.print(" , ");
			}
		}
		System.out.println();
		System.out.println();
	}
	
	private Simbolo getParteIzquierdaFromMap(ParteDerecha parteDerecha){
		Iterator<ParteDerecha> it = partesDerechas.keySet().iterator();
		Simbolo result = null;
		Boolean foundPD = false;
		ParteDerecha actual;
		while(it.hasNext() && !foundPD){
			actual = it.next();
			if(actual.compareTo(parteDerecha) == 0) result = this.partesDerechas.get(actual); 
		}
		
		return result;
	}
	
}
